package org.jhub1.system.xmpp.proxy;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;

import org.jhub1.system.xmpp.commands.ElementLogin;
import org.jhub1.system.xmpp.commands.ElementLogout;
import org.jhub1.system.xmpp.commands.NewMessageProcessor;
import org.jhub1.system.xmpp.commands.RequestMessage;
import org.jhub1.system.xmpp.commands.ReturnMessage;
import org.jhub1.system.xmpp.config.ConfigAdapter;
import org.jhub1.system.xmpp.gateway.packet.IQGatewayItems;
import org.jhub1.system.xmpp.gateway.packet.IQProviderGateway;
import org.jhub1.system.xmpp.log.LogAdapter;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.Roster.SubscriptionMode;
import org.jivesoftware.smack.filter.IQTypeFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.provider.ProviderManager;

public final class XMPPProxy {

	private static LogAdapter log = LogAdapter.getLog(XMPPProxy.class);

	private static ConnectionConfiguration connConfig;
//	@SuppressWarnings("unused")
//	private static MessagesProcessor mp;
	private static IQProcessor iqp;
	private static ConfigAdapter config = ConfigAdapter.getInstance(log);
	private static NewMessageProcessor mproc;
	static XMPPConnection connection;
	private PacketFilter filterMessage = 	new PacketTypeFilter(Message.class);
	private PacketFilter filterIQ = 		new IQTypeFilter(IQ.Type.GET);
	private PacketFilter filterPresence =	new PacketTypeFilter(Presence.class);
	private static Presence presence = new Presence(Presence.Type.available);

	public XMPPProxy(NewMessageProcessor nmproc) throws XMPPException, IOException {
		
		try {
			// Create file
			FileWriter fstream = new FileWriter(config.getPIDFile());
			BufferedWriter out = new BufferedWriter(fstream);
			
			byte[] bo = new byte[100];
			String[] cmd = {"bash", "-c", "echo $PPID"};
			Process p = Runtime.getRuntime().exec(cmd);
			p.getInputStream().read(bo);
			String pid = new String(bo);
			String cpid = pid.replaceAll("\\\n", "");
			out.write(cpid);
			// Close the output stream
			out.close();
			log.debug("PID: " + cpid);
		} catch (Exception e) {// Catch exception if any
			log.error("Saving PID error: " + e.toString());
		}

		XMPPProxy.mproc = nmproc;
		mproc.registerElement("bye", new ElementLogout());
		mproc.registerElement("?StartSession", new ElementLogin());
		
		connConfig = new ConnectionConfiguration(config.getServer(),
				config.getPort(), config.getDomain());
		connection = new XMPPConnection(connConfig);
		Connection.DEBUG_ENABLED = config.isJavaDebugEnabled();
		connection.createPacketCollector(filterMessage);
		connection.createPacketCollector(filterIQ);
		ProviderManager pm = ProviderManager.getInstance();
		pm.addIQProvider("query", "jhub1:iq:gateway", new IQProviderGateway());

		try {
			SASLAuthentication.supportSASLMechanism("PLAIN", 0);
			connection.connect();
			connection.login(config.getNick(), config.getPassword(),
					config.getResource());
		} catch (XMPPException ex) {
			log.fatal("Unable to connect: " + ex.toString());
		}

		Roster.setDefaultSubscriptionMode(SubscriptionMode.manual); 
		
		getResultConnection();
		getXmppRoster();

	//	mp = new MessagesProcessor(config);
		iqp = new IQProcessor(config);
	}

	public static void setStatus(String status) {
		presence.setStatus(status);
		presence.setPriority(1);
		connection.sendPacket(presence);
	}

	public void getResultConnection() throws IOException {
		String result = null;
		if (connection.isConnected() == true
				&& connection.isAuthenticated() == true) {
			result = "Connect to " + connection.getHost() + "; " + "JID - "
					+ connection.getUser();
			log.debug(result);
		}
		if (connection.isConnected() == true
				&& connection.isAuthenticated() == false) {
			result = "Not connect to " + connection.getHost() + "!";
			log.error(result);
		}
		if (connection.isConnected() == false) {
			log.error("Unable to connect");
			System.exit(0);
		}
	}

	public static String getXmppRoster() {
		StringBuilder msg = new StringBuilder();
		Roster roster = connection.getRoster();
		roster.reload();
		Collection<RosterEntry> entries = roster.getEntries();
		Iterator<RosterEntry> it = entries.iterator();
		while (it.hasNext()) {
			msg.append(it.next().toString() + "; ");
		}
		log.info("Roster content: " + msg.toString());
		return msg.toString();
	}

	public static void disconnect() throws IOException {
		log.info("Shutting down!!!");
		connection.disconnect();
		if (connection.isConnected() == false) {
			log.debug("Offline");
		}
		System.exit(0);
	}

	public static void sendMessage(String to_jid, String msg)
			throws XMPPException {
		if (connection == null) {
			throw new XMPPException(
					"Lost connection detected during Message sending.");
		}

		ChatManager chatmanager = connection.getChatManager();
		Chat newChat = chatmanager.createChat(to_jid, new MessageListener() {
			@Override
			public void processMessage(Chat chat, Message message) {
			}
		});
		newChat.sendMessage(msg);

	}
	
	public static void sendMessage(ReturnMessage rm) throws XMPPException, IOException {
		if (rm.getMessage() != null) {
			sendMessage(rm.getJid(), rm.getMessage());
		}
		log.debug(rm.getLogMessage());
	}

	public static void sendIQ(String to_jid, String from_jid, IQGatewayItems iq)
			throws XMPPException {
		if (connection == null) {
			throw new XMPPException(
					"Lost connection detected during IQ sending.");
		}
		iq.setType(IQ.Type.RESULT);
		iq.setTo(to_jid);
		iq.setFrom(from_jid);
		connection.sendPacket(iq);
	}

	private static void parseMessagePacket(Message message)
			throws XMPPException, IOException {
		String jid = message.getFrom();
		String msg = message.getBody();
		if (msg != null) {
			ReturnMessage rm = mproc.process(new RequestMessage(msg, jid));
			if (rm != null) {
				sendMessage(rm);
			}
		}
	}

	private static void parseIQPacket(IQ iq) throws XMPPException, IOException {
		String jidTo = iq.getFrom();
		String jidFrom = iq.getTo();
		IQGatewayItems gwi = (IQGatewayItems) iq;
		log.debug(iq.getChildElementXML());
		@SuppressWarnings("unused")
		boolean success = iqp.process(gwi, jidTo, jidFrom);
		if (ProcessorSession.isMirrorIQActive()) {
			log.info("Mirror IQ is active, sent as a message");
			sendMessage(jidTo, "JHUB1(IQ):\n" + gwi.getChildElementXML());
		}
	}

	private static void parsePresence(Presence presence) throws XMPPException,
			IOException {
		String jidTo = presence.getFrom();
		String jidFrom = presence.getTo();
		String pres = presence.getType().toString();
		if(pres.equals("subscribe")) {
			//Check the accepted list
			if(config.isApproved(jidTo)) {
				//subscribe
				Presence subscribed = new Presence(Presence.Type.subscribed);
				subscribed.setTo(jidTo);
				connection.sendPacket(subscribed);
				//add to roster
				Roster roster = connection.getRoster();
				String[] groups = new String[1];
				groups[0] = "JHUB1Client";
				roster.createEntry(jidTo, jidTo, groups);
				
				log.debug("'subscribe' from: " + jidTo + " ACCEPTED");
			} else {
				log.debug("'subscribe' from: " + jidTo + " REJECTED! - not on white list");
			}
		}
		
		if(pres.equals("unsubscribe")) {
			Presence unsubscribed = new Presence(Presence.Type.unsubscribed);
			unsubscribed.setTo(jidTo);
			connection.sendPacket(unsubscribed);
			// update roster
			Roster roster = connection.getRoster();
			RosterEntry re = roster.getEntry(jidTo);
			roster.removeEntry(re);
			
			log.debug("'unsubscribe' from: " + jidTo + " ACCEPTED");
		}
		
		if(pres.equals("unsubscribe") || pres.equals("subscribe")) {
		//	setStatus(config.getInitialStatus());
		}
		getXmppRoster();
		if(config.isPresenceLogEnabled()) {
			log.info("PRESENCE: " + jidTo + " " + jidFrom + " "
				+ presence.toString());
			log.info(presence.toXML().toString());
		}
	}

	public void incomeChat() throws InterruptedException {

		PacketListener messageListener = new PacketListener() {
			@Override
			public void processPacket(Packet ppacket) {
				Message packet = (Message) ppacket;
				try {
					try {
						parseMessagePacket((Message) ppacket);
					} catch (IOException ex) {
						log.error(ex.toString());
					}
				} catch (XMPPException ex) {
					log.error(ex.toString());
				}
				packet.removeExtension((PacketExtension) packet);
			}
		};

		PacketListener iqListener = new PacketListener() {

			@Override
			public void processPacket(Packet packet) {
				try {
					try {
						parseIQPacket((IQ) packet);
					} catch (IOException ex) {
						log.error(ex.toString());
					}
				} catch (XMPPException ex) {
					log.error(ex.toString());
				}
				packet.removeExtension((PacketExtension) packet);
			}
		};

		PacketListener presenceListener = new PacketListener() {

			@Override
			public void processPacket(Packet packet) {
				try {
					try {
						parsePresence((Presence) packet);
					} catch (IOException ex) {
						log.error(ex.toString());
					}
				} catch (XMPPException ex) {
					log.error(ex.toString());
				}
				packet.removeExtension((PacketExtension) packet);
				setStatus(config.getInitialStatus());
			}
		};

		connection.addPacketListener(iqListener, filterIQ);
		connection.addPacketListener(messageListener, filterMessage);
		connection.addPacketListener(presenceListener, filterPresence);
	}
}